﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using t = System.Threading;
using Dycox.Workflows.Manipulation;

namespace Dycox.Workflows
{
    public abstract class WorkflowExecutionContext : StepContext, IDisposable
    {
        [ThreadStatic]
        static WorkflowExecutionContext _currentContext;

        internal static WorkflowExecutionContext Current
        {
            get { return _currentContext; }
        }

        internal WorkflowExecutionContext(Workflow workflow, OperationArguments args)
            : base(null, workflow)
        {
            if (args == null)
                throw new ArgumentNullException("args");

            if (args.WorkflowName != null && args.WorkflowName != workflow.Name)
                throw new ArgumentException(SR.GetString("invalid_workflow_name", args.WorkflowName));

            _arguments = args;
        }

        internal WorkflowExecutionContext(WorkflowExecutionContext parent, WFStep step)
            : base(step, parent.Workflow)
        {
            _arguments = new OperationArguments();
            _arguments.UserData = CreateContract();
            _arguments.ComponentID = step.ID;
            _arguments.UserData.Comment = step.Comment;
            _arguments.UserData.OperatorName = step.OperatorName;
            _arguments.AppObject = parent.AppObject;

            _dataProvider = parent.DataProvider;
            _parentContext = parent;
            _executing = true;
        }

        

        private WorkflowExecutionContext _parentContext;

        public WorkflowExecutionContext ParentContext
        {
            get { return _parentContext; }
        }


        bool _executing;

        protected bool Executing
        {
            get { return _executing; }
        }

        internal virtual void BeginExecute()
        {
            if (_executing)
                throw new InvalidOperationException(SR.GetString("cannot_BeginExecute_while_executing"));

            EnsureThreadExclusive();

            _executing = true;
            InitializeOperationArguments();
            SetComponent(GetContextComponent());

        }

        private bool EnsureThreadExclusive()
        {
            if (_currentContext == null)
                _currentContext = this;
            else if (!object.ReferenceEquals(_currentContext, this))
            {
                if (AllowsNonExclusiveMode)
                    return false;
                else
                    throw new InvalidOperationException(SR.GetString("one_context_allowed"));
            }

            return true;
        }

        protected virtual bool AllowsNonExclusiveMode
        {
            get { return false; }
        }

        protected abstract IWorkflowComponent GetContextComponent();

        private void InitializeOperationArguments()
        {
            var args = this.Arguments;

            if (args.AppObject == null) args.AppObject = GetAppObject();

        }

        protected virtual IReflectionRoot GetAppObject()
        {
            IRequiresWorkflowReflection rr = this.Workflow as IRequiresWorkflowReflection;
            OperationArguments args = this.Arguments;

            if (rr != null)
            {
                object ds = args.DataSource ?? DataProvider.DataSource;
                var appObj = rr.GetAppObject(args.ComponentID.ProcessId, ds);
                if (appObj == null)
                    throw new WorkflowException(SR.GetString("appobj_not_found"));
                else if (appObj.ProcessId != args.ComponentID.ProcessId)
                    throw new WorkflowException(SR.GetString("id_not_match"));
                return appObj;

            }
            else
                return null;

        }

        internal virtual void EndExecute(bool submitChanges)
        {
            if (!_executing)
                throw new WorkflowException(SR.GetString("context_not_executing"));

            if (submitChanges)
            {
                if (_dataProvider == null)
                    throw new InvalidOperationException(SR.GetString("dataprovider_not_initialized"));
                
                _dataProvider.SubmitChanges();
            }
            DetachCurrentContext();
            _executing = false;

        }

        private void DetachCurrentContext()
        {
            if (object.ReferenceEquals(_currentContext, this))
                _currentContext = null;
        }

        private OperationArguments _arguments;

        public OperationArguments Arguments
        {
            get { return _arguments; }
        }

        private DataProvider _dataProvider;
        private bool _ownsDataProvider;

        internal DataProvider DataProvider
        {
            get
            {
                if (_dataProvider == null)
                {
                    _dataProvider = _arguments.CreateProvider();
                    _ownsDataProvider = true;
                }
                return _dataProvider;
            }
        }

        internal void SetDataProvider(DataProvider value)
        {
            if (value == null)
                throw new ArgumentNullException("value");
            if (_dataProvider != null)
                throw new InvalidOperationException(SR.GetString("dataprovider_already_set"));

            _dataProvider = value;
        }

        public StageOperationContract UserState
        {
            get { return _arguments.UserData; }
        }


        public IReflectionRoot AppObject
        {
            get
            {
                return _arguments.AppObject;
            }
        }

        void IDisposable.Dispose()
        {
            DetachCurrentContext();
            if (_dataProvider != null && _ownsDataProvider)
                _dataProvider.Dispose();
        }
    }
}
